/*
@file	Vector.cpp
@author	Saume
*/

#include "Vector.h"

Vector::Vector(): 
x(0), 
y(0), 
z(0) {

}

Vector::Vector(float x, float y, float z) :
x(x),
y(y),
z(z) {

}

/*
Adds the given vector to the current one.
*/
void Vector::Add(const Vector* const v) {
	x += v->x;
	y += v->y;
	z += v->z;
	return;
}

/*
Substracts the given vector from the current one.
*/
void Vector::Substract(const Vector* const v) {
	x -= v->x;
	y -= v->y;
	z -= v->z;
	return;
}

/*
Scales the current vector by the given scalar.
*/
void Vector::Scale(float s) {
	x *= s;
	y *= s;
	z *= s;
	return;
}

/*
Returns the dot product of the current vecotr with the given one.
*/
float Vector::Dot(const Vector* const v) const {
	return x * v->x + y * v->y + z * v->z;
}

/*
Returns the cross product of the current vector with the given one.
Considers a left-handed coordinate system, so the z values of vectors are negated.
Returns a pointer to a new vector.
*/
Vector* Vector::CrossLH(const Vector* const v) const {
	Vector* r = new Vector((y * -(v->z) - -(z) * v->y), -(x * -(v->z) - -(z) * v->x), (x * v->y - y * v->x));
	return r;
}

/*
Returns the cross product of the current vector with the given one.
Considers a normal coordinate system.
Returns a new vector.
*/
Vector* Vector::CrossRH(const Vector* const v) const {
	Vector* r = new Vector((y * v->z - z * v->y), -(x * v->z - z * v->x), (x * v->y - y * v->x));
	return r;
}

/*
Considers the current vector to be a point and moves it by the given mBy vector.
*/
void Vector::MoveBy(const Vector* const mBy) {
	Add(mBy);
	return;
}

/*
Returns the counterclockwise angle of the vector from the x axis from 0 to 360 degrees.
For 2D vectors, does not consider Z axis.
*/
float Vector::GetPolarAngleDeg() const {
	float angle;
	float xAbs = fabs(x);
	float yAbs = fabs(y);
	
	if (xAbs <= EPSILON_DBL) { // would divide by 0
		if (yAbs <= EPSILON_DBL) { // null vector
			angle = 0.0f; // so the angle would be 0 by default
		} else {
			angle = PI / 2.0f; // angle in rads, equivalent to 90 degrees
		}
	} else {
		if (yAbs <= EPSILON_DBL) { // the vector is on the x axis
			angle = 0.0f; // so the angle would be 0 by default
		} else {
			angle = float(atan(((y < 0.0f) ? -(y) : y) / ((x < 0.0f) ? -(x) : x))); // angle in rads
		}
	}
	
	angle = abs(angle); // use the absolute value of the angle to adjust it according to quadrant
	angle = float(angle * HALF_CIRCLE / PI); // convert to degs, angle between 0 and 90

	if (x >= 0.0f) {
		if (y >= 0.0f) { // top right
			return angle;
		} else  { // bottom right
			return FULL_CIRCLE - angle;
		}
	} else {
		if (y >= 0) { // top left
			return HALF_CIRCLE - angle;
		} else  { // bottom left
			return HALF_CIRCLE + angle;
		}
	}
}

/*
Returns the shortest angle between the current vector and the given one.
*/
float Vector::AngleBetweenDeg(const Vector* const v) const {
	float l1 = Length();
	float l2 = v->Length();
	float angle;
	
	if (l1 <= EPSILON_DBL || l2 <= EPSILON_DBL) { // would divide by 0
		angle = 0.0f; // so by default the angle will be 0
	} else {
		angle = float(acos(Dot(v) / (l1 * l2))); // angle in rads
		angle = fabs(angle); // use the absolute value of the angle to adjust it according to quadrant
		angle = float(angle * HALF_CIRCLE / PI); // convert to degs, angle between 0 and 90
	}

	if (angle > HALF_CIRCLE / 2.0f) {
		angle = HALF_CIRCLE - angle;
	}

	return angle;
}

/*
Returns the length of the vector.
*/
float Vector::Length() const {
	return sqrt(LengthSq());
}

/*
Returns the square of the length of the vector.
*/
float Vector::LengthSq() const {
	return x * x + y * y + z * z;
}

/*
Returns true if the vector can be normailzed and was effectively normalized, false otherwise.
*/
bool Vector::Normalize() {
	float xAbs = fabs(x);
	float yAbs = fabs(y);
	float zAbs = fabs(z);

	if (xAbs <= EPSILON_DBL && yAbs <= EPSILON_DBL && zAbs <= EPSILON_DBL) { // null vector
		return false; // the vector can not be normalized
	} else {
		float l = Length();
		x /= l;
		y /= l;
		z /= l;

		return true;
	}
}

/*
Displays the information of the vector in the console.
*/
void Vector::Display() const {
	std::cout << "(" << x << ", " << y << ", " << z << ")";

	return;
}

/*
Returns a unitary vector for the given polar angle.
For 2D vectors, does not consider Z axis.
*/
Vector* Vector::GetVectorByAngleDeg(float ang) {
	float angle = float(ang * PI / HALF_CIRCLE); // convert the angle to rads
	Vector* r = new Vector(cosf(angle) * UNIT_LENGTH, sinf(angle) * UNIT_LENGTH, 0);
	return r;
}

/*
Returns wether two floats are within EPSILON_DBL of eachother or not, which means they are effectively equal.
*/
bool FloatEquals(float f1, float f2) {
	return fabs(f1 - f2) <= EPSILON_DBL;
}

/*
Returns true if the two given vectors are equipollent, false otherwise.
*/
bool operator==(const Vector& v1, const Vector& v2) {
	float dx = v1.x - v2.x;
	float dy = v1.y - v2.y;
	float dz = v1.z - v2.z;
	dx = fabs(dx);
	dy = fabs(dy);
	dz = fabs(dz);

	return dx <= EPSILON_DBL && dy <= EPSILON_DBL && dz <= EPSILON_DBL;
}

Vector* operator*(const Vector& v, float s) {
	Vector* r = new Vector(v);
	r->Scale(s);
	return r;

}

Vector* operator*(const Vector& v, int s) {
	Vector* r = new Vector(v);
	r->Scale(s);
	return r;
}

Vector* operator*(float s, const Vector& v) {
	Vector* r = new Vector(v);
	r->Scale(s);
	return r;
}

Vector* operator*(int s, const Vector& v) {
	Vector* r = new Vector(v);
	r->Scale(s);
	return r;
}

float operator*(const Vector& v1, const Vector& v2) {
	return v1.Dot(&v2);
}

Vector* operator+(const Vector& v1, const Vector& v2) {
	Vector* r = new Vector(v1);
	r->Add(&v2);
	return r;
}

Vector* operator-(const Vector& v1, const Vector& v2) {
	Vector* r = new Vector(v1);
	r->Substract(&v2);
	return r;
}